前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • Vue

    • 从源码解读Vue生命周期
    • 组件的本质
    • 有状态组件的设计
    • 设计 VNode
    • 辅助创建 VNode 的 h 函数
    • 自定义渲染器和异步渲染
    • 渲染器之挂载
    • 渲染器的核心 Diff 算法
    • 渲染器之patch
    • 图解 Vue 响应式原理
    • 图解 Vue 异步更新
    • 剖析 Vue 内部运行机制
    • vue响应式原理模拟
    • vue状态管理之vuex
    • 理解Vue的设计思想及实现Vue
    • diff算法深入
    • vue router vuex原理分析
    • Vue3初探响应式原理.
    • vue2源码分析
    • vue组件化实践
  • React

  • Webpack

  • Node

  • Javascript

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

利用 Webpack CodeSplitting 完成复杂应用拆包|原理篇

文章学习导航
面试总览

30 秒速记

  • 核心判断:构建工具从入口解析依赖图,经转换和优化生成可在目标运行时加载的模块与资源
  • 原理主线:围绕 「All in One 的弊端」、「Code Splitting」、「写在最后」 建立输入、状态变化与输出之间的因果关系
  • 文章范围:介绍了 Webpack 的 Code Splitting(代码分割)原理及其在大型前端应用中的应用,分析了 All in One 打包方式的弊端,并讲解了如何通过多入口和动态导入实现按需加载与优化性能,帮助开发者高效拆分和管理复杂项目的打包
  • 边界与代价:静态分析受模块语法、动态依赖和副作用声明限制;拆包过细也会增加请求和运行时成本
  • 工程落地:优化前先测构建阶段、产物组成和缓存命中,再针对解析、转换、压缩或运行时逐层处理

复杂应用应通过多入口和动态 import() 做代码拆分,让启动阶段只加载当前真正需要的资源。 多入口更适合多页应用,每个页面对应一个入口,再用 splitChunks 提取公共模块,避免重复打包。单页应用通常按路由或功能动态导入,Webpack 会把相关模块拆成独立 chunk,运行到对应逻辑时再加载。拆包也不是越细越好,请求开销与公共缓存都要考虑;需要稳定命名时可使用 webpackChunkName 魔法注释。

这篇文章不要按 API 清单来背。先用上面的 Mind Map 建立全局结构,再通过交互 DEMO 观察正常路径和边界路径如何改变状态;阅读正文时重点核对每一步的输入、负责执行的参与者、产生的中间状态以及最终可观察结果。遇到版本敏感结论,要把“历史实现”“当前行为”和“工程兼容策略”分开说明;遇到性能或架构取舍,则用实际指标、失败现象和验证手段支撑判断。

版本校准: 旧文中的 webpack 4、JSONP 更新清单或 react-hot-loader 代码用于解释历史链路,不应直接复制到新项目。当前 webpack-dev-server 4+ 默认启用 HMR,严格 ESM 可使用 import.meta.webpackHot;生产环境不得携带 HMR runtime。以 webpack HMR 官方指南 和项目锁定版本为准。

# All in One 的弊端

通过 Webpack 实现前端项目整体模块化的优势固然明显,但是它也会存在一些弊端:它最终会将我们所有的代码打包到一起。试想一下,如果我们的应用非常复杂,模块非常多,那么这种 All in One 的方式就会导致打包的结果过大,甚至超过 4 ~ 5M。

在绝大多数的情况下,应用刚开始工作时,并不是所有的模块都是必需的。如果这些模块全部被打包到一起,即便应用只需要一两个模块工作,也必须先把 bundle.js 整体加载进来,而且前端应用一般都是运行在浏览器端,这也就意味着应用的响应速度会受到影响,也会浪费大量的流量和带宽。

所以这种 All in One 的方式并不合理,更为合理的方案是把打包的结果按照一定的规则分离到多个 bundle 中,然后根据应用的运行需要按需加载。这样就可以降低启动成本,提高响应速度。

其实这并不矛盾,只是物极必反罢了。Web 应用中的资源受环境所限,太大不行,太碎更不行。因为我们开发过程中划分模块的颗粒度一般都会非常的细,很多时候一个模块只是提供了一个小工具函数,并不能形成一个完整的功能单元。

如果我们不将这些资源模块打包,直接按照开发过程中划分的模块颗粒度进行加载,那么运行一个小小的功能,就需要加载非常多的资源模块。

再者,目前仍是主流(🐶)的 HTTP 1.1 本身就存在一些缺陷,例如:

  • 同一个域名下的并行请求是有限制的;
  • 每次请求本身都会有一定的延迟;
  • 每次请求除了传输内容,还有额外的请求头,大量请求的情况下,这些请求头加在一起也会浪费流量和带宽。

综上所述,模块打包肯定是必要的,但当应用体积越来越大时,我们也要学会变通。

# Code Splitting

为了解决打包结果过大导致的问题,Webpack 设计了一种分包功能:Code Splitting(代码分割)。

Code Splitting 通过把项目中的资源模块按照我们设计的规则打包到不同的 bundle 中,从而降低应用的启动成本,提高响应速度。

Webpack 实现分包的方式主要有两种:

  • 根据业务不同配置多个打包入口,输出多个打包结果;
  • 结合 ES Modules 的动态导入(Dynamic Imports)特性,按需加载模块。

# 多入口打包

多入口打包一般适用于传统的多页应用程序,最常见的划分规则就是一个页面对应一个打包入口,对于不同页面间公用的部分,再提取到公共的结果中。

.
├── dist
├── src
│   ├── common
│   │   ├── fetch.js
│   │   └── global.css
│   ├── album.css
│   ├── album.html
│   ├── album.js
│   ├── index.css
│   ├── index.html
│   └── index.js
├── package.json
└── webpack.config.js
@前端进阶之旅: 代码已经复制到剪贴板

这个示例中有两个页面,分别是 index 和 album。代码组织的逻辑也很简单:

  • index.js: 负责实现 index 页面功能逻辑;
  • album.js: 负责实现 album 页面功能逻辑;
  • global.css: 是公用的样式文件;
  • fetch.js: 是一个公用的模块,负责请求 API。

我们回到配置文件中,这里我们尝试为这个案例配置多入口打包,具体配置如下:

// ./webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')

/** @type {import('webpack').Configuration} */
module.exports = {
  entry: {
    index: './src/index.js',
    album: './src/album.js'
  },
  output: {
    filename: '[name].bundle.js' // [name] 是入口名称
  },
  // ... 其他配置
  plugins: [
    new HtmlWebpackPlugin({
      title: 'Multi Entry',
      template: './src/index.html',
      filename: 'index.html'
    }),
    new HtmlWebpackPlugin({
      title: 'Multi Entry',
      template: './src/album.html',
      filename: 'album.html'
    })
  ]
}
@前端进阶之旅: 代码已经复制到剪贴板

一般 entry 属性中只会配置一个打包入口,如果我们需要配置多个入口,可以把 entry 定义成一个对象。

注意:这里 entry 是定义为对象而不是数组,如果是数组的话就是把多个文件打包到一起,还是一个入口。

在这个对象中一个属性就是一个入口,属性名称就是这个入口的名称,值就是这个入口对应的文件路径。那我们这里配置的就是 index 和 album 页面所对应的 JS 文件路径。

一旦我们的入口配置为多入口形式,那输出文件名也需要修改,因为两个入口就有两个打包结果,不能都叫 bundle.js。我们可以在这里使用 [name] 这种占位符来输出动态的文件名,[name] 最终会被替换为入口的名称。

除此之外,在配置中还通过 html-webpack-plugin 分别为 index 和 album 页面生成了对应的 HTML 文件。

完成配置之后,我们就可以打开命令行终端,运行 Webpack 打包,那此次打包会有两个入口。打包完成后,我们找到输出目录,这里就能看到两个入口文件各自的打包结果了,如下图所示:

但是这里还有一个小问题,我们打开任意一个输出的 HTML 文件,具体结果如下图:

你就会发现 index 和 album 两个打包结果都被页面载入了,而我们希望的是每个页面只使用它对应的那个输出结果。

所以这里还需要修改配置文件,我们回到配置文件中,找到输出 HTML 的插件,默认这个插件会自动注入所有的打包结果,如果需要指定所使用的 bundle,我们可以通过 HtmlWebpackPlugin 的 chunks 属性来设置。我们分别为两个页面配置使用不同的 chunk,具体配置如下:

💡:每个打包入口都会形成一个独立的 chunk(块)。

// ./webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')

/** @type {import('webpack').Configuration} */
module.exports = {
  entry: {
    index: './src/index.js',
    album: './src/album.js'
  },
  output: {
    filename: '[name].bundle.js' // [name] 是入口名称
  },
  // ... 其他配置
  plugins: [
    new HtmlWebpackPlugin({
      title: 'Multi Entry',
      template: './src/index.html',
      filename: 'index.html',
      chunks: ['index'] // 指定使用 index.bundle.js
    }),
    new HtmlWebpackPlugin({
      title: 'Multi Entry',
      template: './src/album.html',
      filename: 'album.html',
      chunks: ['album'] // 指定使用 album.bundle.js
    })
  ]
}
@前端进阶之旅: 代码已经复制到剪贴板

完成以后我们再次回到命令行终端,然后运行打包,打包结果如下图:

这一次打包的结果就完全正常了。

那这就是配置多入口打包的方法,以及如何指定在 HTML 中注入的 bundle。

# 提取公共模块

多入口打包本身非常容易理解和使用,但是它也存在一个小问题,就是不同的入口中一定会存在一些公共使用的模块,如果按照目前这种多入口打包的方式,就会出现多个打包结果中有相同的模块的情况。

例如我们上述案例中,index 入口和 album 入口中就共同使用了 global.css 和 fetch.js 这两个公共的模块。这里是因为我们的示例比较简单,所以重复的影响没有那么大,但是如果我们公共使用的是 jQuery 或者 Vue.js 这些体积较大的模块,那影响就会比较大,不利于公共模块的缓存。

所以我们还需要把这些公共的模块提取到一个单独的 bundle 中。Webpack 中实现公共模块提取非常简单,我们只需要在优化配置中开启 splitChunks 功能就可以了,具体配置如下:

// ./webpack.config.js
/** @type {import('webpack').Configuration} */
module.exports = {
  entry: {
    index: './src/index.js',
    album: './src/album.js'
  },
  output: {
    filename: '[name].bundle.js' // [name] 是入口名称
  },
  optimization: {
    splitChunks: {
      // 自动提取所有公共模块到单独 bundle
      chunks: 'all'
    }
  }
  // ... 其他配置
}
@前端进阶之旅: 代码已经复制到剪贴板

← 前端构建新玩法 Vite 上手与思考玩转 Webpack 的 TreeShaking 与 sideEffects 特性 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • Vue

    • 从源码解读Vue生命周期
    • 组件的本质
    • 有状态组件的设计
    • 设计 VNode
    • 辅助创建 VNode 的 h 函数
    • 自定义渲染器和异步渲染
    • 渲染器之挂载
    • 渲染器的核心 Diff 算法
    • 渲染器之patch
    • 图解 Vue 响应式原理
    • 图解 Vue 异步更新
    • 剖析 Vue 内部运行机制
    • vue响应式原理模拟
    • vue状态管理之vuex
    • 理解Vue的设计思想及实现Vue
    • diff算法深入
    • vue router vuex原理分析
    • Vue3初探响应式原理.
    • vue2源码分析
    • vue组件化实践
  • React

  • Webpack

  • Node

  • Javascript

  • 综合